// ==UserScript==
// @name        0525XHStest01
// @namespace    http://tampermonkey.net/
// @version      2025-05-25
// @description  try to take over the world!
// @author       You
// @match        https://www.xiaohongshu.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=xiaohongshu.com
// @require      https://scriptcat.org/lib/2691/1.0.0/sweetalert2.all.min-11.15.10.js
// @grant        GM.setValue
// @grant        GM.getValue
// @grant        GM.deleteValue
// @grant        unsafeWindow
// @grant        GM_xmlhttpRequest
// @run-at       document-body
// ==/UserScript==

(function() {
    'use strict';
    // class RequestMonitor {
    //     constructor() {
    //         // 预编译正则表达式提升匹配性能
    //         this._patterns = new Map(); // Map<RegExp, Processor>

    //         // 使用WeakMap防止内存泄漏
    //         this.pendingRequests = new WeakMap();

    //         // 缓存已验证的URL
    //         this._urlCache = {
    //             monitored: new Set(),
    //             ignored: new Set()
    //         };

    //         // 性能统计
    //         this.stats = {
    //             total: 0,
    //             processed: 0,
    //             errors: 0
    //         };

    //         this._initHooks();
    //     }
    //     /* pattern:正则表达式(只要满足其一就会进行监听处理)，processor：处理器函数(对满足的每一个pattern进行processor处理，一对多) */
    //     registerHandler(pattern, processor) {
    //         const regex = new RegExp(pattern);
    //         this._patterns.set(regex, processor);
    //         return this; // 支持链式调用
    //     }

    //     _initHooks() {
    //         // 保存原生方法引用
    //         const nativeFetch = unsafeWindow.fetch;
    //         const nativeXHROpen = XMLHttpRequest.prototype.open;
    //         const nativeXHRSend = XMLHttpRequest.prototype.send;
    //         const nativeXHRSetRequestHeader = XMLHttpRequest.prototype.setRequestHeader;

    //         const self = this;

    //         // Hook XHR open方法捕获URL
    //         XMLHttpRequest.prototype.open = function (method, url) {
    //             this._requestURL = url;
    //             return nativeXHROpen.apply(this, arguments);
    //         };

    //         // Hook XHR setRequestHeader 方法记录请求头
    //         XMLHttpRequest.prototype.setRequestHeader = function (header, value) {
    //             if (!this._headers) this._headers = new Map();
    //             this._headers.set(header, value);
    //             return nativeXHRSetRequestHeader.apply(this, arguments);
    //         };

    //         // Hook XHR send
    //         XMLHttpRequest.prototype.send = function (body) {
    //             const xhr = this;
    //             const url = xhr._requestURL || '';

    //             // 前置检查
    //             if (!self._shouldMonitor(url)) {
    //                 return nativeXHRSend.apply(this, arguments);
    //             }

    //             const requestKey = {};
    //             requestKey.headers = new Map(xhr._headers); // 复制请求头
    //             self.pendingRequests.set(xhr, requestKey);
    //             self.stats.total++;

    //             // 添加事件监听
    //             xhr.addEventListener('load', () => self._handleXHRResponse(xhr, requestKey));
    //             xhr.addEventListener('error', () => self._cleanup(xhr));

    //             nativeXHRSend.apply(this, arguments);
    //         };

    //         // Hook Fetch
    //         unsafeWindow.fetch = async (input, init) => {
    //             const url = typeof input === 'string' ? input : input.url;

    //             if (!self._shouldMonitor(url)) {
    //                 return nativeFetch(input, init);
    //             }

    //             const requestKey = {};
    //             self.pendingRequests.set(requestKey, true);
    //             self.stats.total++;

    //             try {
    //                 const response = await nativeFetch(input, init);
    //                 self._handleFetchResponse(url, response.clone(), requestKey);
    //                 return response;
    //             } catch (error) {
    //                 self.stats.errors++;
    //                 self._cleanup(requestKey);
    //                 throw error;
    //             }
    //         };
    //     }

    //     _shouldMonitor(url) {
    //         if (this._urlCache.monitored.has(url)) return true;
    //         if (this._urlCache.ignored.has(url)) return false;

    //         for (const [regex] of this._patterns) {
    //             if (regex.test(url)) {
    //                 this._urlCache.monitored.add(url);
    //                 return true;
    //             }
    //         }

    //         this._urlCache.ignored.add(url);
    //         return false;
    //     }
        
    //     async _handleXHRResponse(xhr, requestKey) {
    //         try {
    //             const data = await this._parseResponse(xhr.response);
    //             await this._processData(xhr._requestURL, data, requestKey);
    //             this.stats.processed++;
    //         } catch (error) {
    //             this.stats.errors++;
    //             console.error('XHR处理失败:', error);
    //         } finally {
    //             this._cleanup(xhr);
    //         }
    //     }

    //     async _handleFetchResponse(url, response, requestKey) {
    //         try {
    //             const data = await this._parseResponse(await response.text());
    //             await this._processData(url, data);
    //             this.stats.processed++;
    //         } catch (error) {
    //             this.stats.errors++;
    //             console.error('Fetch处理失败:', error);
    //         } finally {
    //             this._cleanup(requestKey);
    //         }
    //     }

    //     async _processData(url, rawData, requestKey) {
    //         const resAndHeaders = [rawData, requestKey];
    //         for (const [regex, processor] of this._patterns) {
    //             if (regex.test(url)) {
    //                 await processor(resAndHeaders);
    //                 break; // 仅匹配第一个处理器
    //             }
    //         }
    //     }

    //     async _parseResponse(data) {
    //         try {
    //             return JSON.parse(data);
    //         } catch {
    //             return data;
    //         }
    //     }

    //     _cleanup(target) {
    //         this.pendingRequests.delete(target);

    //         // 定期清理缓存防止内存增长
    //         if (this._urlCache.monitored.size > 1000) {
    //             this._urlCache.monitored.clear();
    //         }
    //     }

    //     // 调试方法
    //     printStats() {
    //         console.table({
    //             '总请求数': this.stats.total,
    //             '已处理请求': this.stats.processed,
    //             '当前挂起请求': this.pendingRequests.size,
    //             '错误数量': this.stats.errors,
    //             '缓存命中率': `${(this.stats.processed / this.stats.total * 100).toFixed(1)}%`
    //         });
    //     }
    // }
    class PostTool {
        constructor(xsCommon, xs) {
          this.xsCommon = xsCommon;
          this.xs = xs;
        }
      
        /**
         * 获取帖子用户信息（GET请求）
         * @param {string} postUrl - 帖子Url
         * @returns {Promise<string>} - 返回服务器响应的原始数据
         */
        getPostUserInfo(postUrl) {
          return new Promise((resolve, reject) => {
            const xhr = new XMLHttpRequest();
            const url = postUrl;
            xhr.open("GET", url, true);
      
            // 设置请求头
            xhr.setRequestHeader("accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7");
            xhr.setRequestHeader("accept-language", "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6");
            xhr.setRequestHeader("cache-control", "no-cache");
            xhr.setRequestHeader("pragma", "no-cache");
            xhr.setRequestHeader("priority", "u=0, i");
            xhr.setRequestHeader("upgrade-insecure-requests", "1");
            xhr.setRequestHeader("Referrer-Policy", "strict-origin-when-cross-origin");
            xhr.withCredentials = true;

            // 成功回调
            xhr.onload = () => {
              if (xhr.status >= 200 && xhr.status < 300) {
                resolve(xhr.responseText);
              } else {
                reject(new Error(`HTTP error! status: ${xhr.status}`));
              }
            };
      
            // 失败回调
            xhr.onerror = () => {
              reject(new Error("Network error"));
            };
      
            xhr.send();
          });
        }
      
        /**
         * 获取帖子具体内容（POST请求）
         * @param {string} postUrl - 帖子url
         * @returns {Promise<string>} - 返回服务器响应的原始数据
         */
        getPostContent(postUrl) {
          return new Promise((resolve, reject) => {
            const xhr = new XMLHttpRequest();
            const url = "https://edith.xiaohongshu.com/api/sns/web/v1/feed";
            const x_t = Date.now();
            const x_xray_traceid = generateXrayTraceId(x_t);
            const x_b3_traceid = generateB3TraceId();
            const x_mns = "unload";
            const paramDict = parseUrlParamsAndPath(postUrl);
            xhr.open("POST", url, true);
      
            // 设置请求头
            xhr.setRequestHeader("accept", "application/json, text/plain, */*");
            xhr.setRequestHeader("accept-language", "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6");
            xhr.setRequestHeader("cache-control", "no-cache");
            xhr.setRequestHeader("content-type", "application/json;charset=UTF-8");
            xhr.setRequestHeader("pragma", "no-cache");
            // xhr.setRequestHeader("priority", "u=1, i");
            xhr.setRequestHeader("x-b3-traceid", "15d77c8ca5686630");
            xhr.setRequestHeader("x-mns", "unload");
            xhr.setRequestHeader("x-t", "1748784983387");
            xhr.setRequestHeader("x-xray-traceid", "cb95da9684537070a81dc245ea6d5622");
            // xhr.setRequestHeader("Referrer-Policy", "strict-origin-when-cross-origin");

            const XS = this._generateHeadersXS("//edith.xiaohongshu.com/api/sns/web/v1/feed",
                 paramDict.source_note_id,
                 x_b3_traceid,
                 x_xray_traceid,
                paramDict.xsec_token).headers["X-s"];
            const XSCommon = this._generateHeadersXSCommon(x_b3_traceid,
                x_xray_traceid,
                x_t,
                XS,
                x_mns,
                "//edith.xiaohongshu.com/api/sns/web/v1/feed",
                "Windows").headers['X-S-Common'];
            xhr.setRequestHeader("X-S-Common", "2UQAPsHC+aIjqArjwjHjNsQhPsHCH0rjNsQhPaHCH0c1PahIHjIj2eHjwjQgynEDJ74AHjIj2ePjwjQhyoPTqBPT49pjHjIj2ecjwjHFN0G7N0ZjNsQh+aHCH0rE+/clP/LA+fH9P9hI4/SY+BkhyBVFPo4VP9+7ydk02o4VyfLEJ789+/ZIPeZU+/DAw/DjNsQh+jHCHjHVHdW7H0ijHjIj2eWjwjQQPAYUaBzdq9k6qB4Q4fpA8b878FSet9RQzLlTcSiM8/+n4MYP8F8LagY/P9Ql4FpUzfpS2BcI8nT1GFbC/L88JdbFyrSiafprJDMra7pFLDDAa7+8J7QgabmFz7Qjp0mcwp4fanD68p40+fp8qgzELLbILrDA+9p3JpH9LLI3+LSk+d+DJfpSL98lnLYl49IUqgcMc0mrcDShtMmozBD6qM8FyFSh8o+h4g4U+obFyLSi4nbQz/+SPFlnPrDApSzQcA4SPopFJeQmzBMA/o8Szb+NqM+c4ApQzg8Ayp8FaDRl4AYs4g4fLomD8pzBpFRQ2ezLanSM+Skc47Qc4gcMag8VGLlj87PAqgzhagYSqAbn4FYQy7pTanTQ2npx87+8NM4L89L78p+l4BL6ze4AzB+IygmS8Bp8qDzFaLP98Lzn4AQQzLEAL7bFJBEVL7pwyS8Fag868nTl4e+0n04ApfuF8FSbL7SQyrpo/f40pLShJpmO2fM6anS0nBpc4F8Q4fSaPDH7qFzC+7+hpdzDagG98nc7+9p8ydp7anD78/8gqflNpd4xanYtqA+68gP9zo8SpbmF/f+p+fpr4gqMag88/Bbm4fp/4g4aqBG6qFzl47QQP9IUagYTJo+l4o+YLo4Eq7+HGDRQ4fprJApSpb87JDSba7+g8DbSPn+zPFSk/d+/qo8SyFQgcLSkafpLLoz+anYIzeYl4ocUpdzgagY98/+c4AbQyURAPgp7+LSbcg+x4gz/t7b7Lokc4MpQ4dp3agY0qDESLnYOqDESygp7aDShz7QQyo8A8bmFqokVagm0NFkAzrQ8PFSkGFIU4gzNJgp7aLSe8BpnLozpaF8S8p8gcgPl/LpPaLLIqA8S8o+kLozYGMm7qDSeafpfpd4fanTdqAGI/oQQcFTS8Bu6qM+n49lQc9zS+dpFyDWIyozQy7i6anTwqAbg87+kN9zApMq78nkl49bQznlianY0zokc4MkQ4f+aHjIj2eDjw0HlPAG9P/PMP/GVHdWlPsHCPsIj2erlH0ijJfRUJnbVHdF=");
            xhr.setRequestHeader("X-S", XS);

            xhr.setRequestHeader("Content-Type", "application/json");
            // xhr.setRequestHeader("origin", "https://www.xiaohongshu.com");

            xhr.withCredentials = true;
            // 成功回调
            xhr.onload = () => {
              if (xhr.status >= 200 && xhr.status < 300) {
                resolve(JSON.parse(xhr.responseText));
              } else {
                reject(new Error(`HTTP error! status: ${xhr.status}`));
              }
            };
      
            // 失败回调
            xhr.onerror = () => {
              reject(new Error("Network error"));
            };
      
            // 发送POST数据

            const postData = this._generatePostData(paramDict.source_note_id, 
                    ["jpg", "webp", "avif"], 
                    { need_body_topic: "1" }, 
                    "pc_feed", 
                    paramDict.xsec_token);
            const data = JSON.stringify(postData);
            xhr.send(data);
          });
        }
        /**
         * 
         * @param {string} source_note_id 
         * @param {Array<string>} image_formats 
         * @param {Object} extra 
         * @param {string} xsec_source 
         * @param {string} xsec_token 
         * @returns {Object} - 生成的POST数据对象
         */
        _generatePostData(source_note_id, image_formats, extra, xsec_source, xsec_token) { 
            const postData = {};
            postData.source_note_id = source_note_id;
            postData.image_formats = image_formats;
            postData.extra =  extra;
            postData.xsec_source = xsec_source;
            postData.xsec_token = xsec_token;
            return postData;
        }
        /**
         * 
         * @param {string} url - 路径e.g. "//edith.xiaohongshu.com/api/sns/web/v1/feed"
         * @param {string} noteId - 笔记id
         * @param {string} xB3Traceid 
         * @param {string} xXrayTracei
         * @param {string} xsecToken 
         * @returns {object} - 带有XS Object
         */
        _generateHeadersXS(url, noteId, xB3Traceid, xXrayTraceid, xsecToken){
            /* 
                经实验请求只对xs敏感，不同xscommon可以复用，xs只对data中的noteId和xesToken敏感
            */
        const xhsTemp1 = {
            "callFrom": "web",
            "carryDeviceInfo": false,
            "includes": [
                "xiaohongshu.com"
            ],
            "process461Response": true,
            "captchaInCurrent": true,
            "isHidden": "visible",
            "platform": "Windows",
            "xsIgnore": [],
            "configInit": true,
            "autoReload": false,
            "disableMns": false,
            "loadTimeout": 4000,
            "appId": "xhs-pc-web"
        }
        const xhsTemp2 = {
            "timeout": 6000,
            "transform": true,
            "withCredentials": true,
            "timeoutErrorMessage": "薯队长遇到了点小麻烦",
            "traceHosts": [
                "www.xiaohongshu.com",
                "logan.devops.xiaohongshu.com",
                "dragon.devops.xiaohongshu.com",
                "www.beta.xiaohongshu.com",
                "edith.xiaohongshu.com",
                "edith.beta.xiaohongshu.com"
            ],
            "useXRayHeader": true,
            "traceIgnores": [
                "www.beta.xiaohongshu.com/api/sec/v1/scripting",
                "www.beta.xiaohongshu.com/api/sec/v1/shield/webprofile",
                "www.xiaohongshu.com/api/sec/v1/sbtsource"
            ],
            "summary": "【web】- feed",
            "data": {
                "source_note_id": noteId,
                "image_formats": [
                    "jpg",
                    "webp",
                    "avif"
                ],
                "extra": {
                    "need_body_topic": "1"
                },
                "xsec_source": "pc_feed",
                "xsec_token": xsecToken
            },
            "method": "POST",
            "url": "//edith.xiaohongshu.com/api/sns/web/v1/feed",
            "matchedPath": "/api/sns/web/v1/feed",
            "headers": {
                "x-b3-traceid": "5af504407db4e98c",
                "x-xray-traceid": "cb9c5dd37d2b402f2297f07d3f3bf7f5",
                "X-t": 1749003572203,
                "X-Mns": "unload"
            },
            "rid": "ffd26062-9a2e-4e0d-8ed7-8688b8676559"
        }
            return unsafeWindow.vvvv(xhsTemp1, xhsTemp2);
        }
        /**
         * 
         * @param {string} x_b3_traceid 
         * @param {string} x_xray_traceid 
         * @param {number} X_t 
         * @param {string} X_s 
         * @param {string} X_Mns 
         * @param {string} url - e.g. "//edith.xiaohongshu.com/api/sns/web/v1/feed"
         * @returns {object} - 生成带有X-S-Common对象
         */
        _generateHeadersXSCommon(x_b3_traceid, x_xray_traceid, X_t, X_s, X_Mns, url, platform){
            const e = {
                "platform": platform
            }
            const r = {
                "headers": {
                    "x-b3-traceid": x_b3_traceid,
                    "x-xray-traceid": x_xray_traceid,
                    "X-t": X_t,
                    "X-s": X_s,
                    "X-Mns": X_Mns
                },
                "url": url
            }
            return unsafeWindow.tttt(e, r);            ;
        }
    }
    class PostProcessor {
        constructor(postData) {
          this.postData = postData;
        }
      
        getTags() {
          const tags = this.postData.data.items[0].note_card.tag_list || [];
          return tags.map(({ id, name }) => ({ id, name }));
        }
      
        getInteractions() {
          return {
            likes: this.postData.likes || 0,
            collects: this.postData.collects || 0,
            comments: this.postData.comments || 0
          };
        }
    }
    class PostStatistics{
        constructor() {
            this.tagList = [];
        }
        /**
         * 
         * @param {Array<Object>} tag 
         */
        addTag(tag) {
            this.tagList.push(...tag);
        }

        countTags() {
            const countMap = {};
          
            // 合并所有数组并遍历每个元素
            this.tagList.forEach(item => {
              const name = item.name;
              if (name) {
                countMap[name] = (countMap[name] || 0) + 1;
              }
            });
          
            // 将统计结果转为数组格式
            return Object.keys(countMap).map(name => ({
              name,
              count: countMap[name]
            }));
          }

        /**
         * 对标签统计结果进行排序
         * @param {Array} stats - 标签统计数组，格式为 [{ name: string, count: number }]
         * @param {string} sortBy - 排序依据，可选 'count' 或 'name'
         * @param {string} order - 排序顺序，可选 'asc'（升序）或 'desc'（降序）
         * @returns {Array} 排序后的新数组
         */
        sortTagStats(stats, sortBy = 'count', order = 'desc') {
            return [...stats].sort((a, b) => {
                if (sortBy === 'count') {
                    const diff = a.count - b.count;
                    return order === 'asc' ? diff : -diff;
                } else if (sortBy === 'name') {
                    const nameA = a.name.toLowerCase();
                    const nameB = b.name.toLowerCase();
                    return order === 'asc'
                    ? nameA.localeCompare(nameB)
                    : nameB.localeCompare(nameA);
                } else {
                    throw new Error(`不支持的排序字段: ${sortBy}`);
                }
            });
        }
    }
    function generateXrayTraceId(timestamp = Date.now()) {
        let sequence = 21;
    
        function getSequence() {
            sequence = (sequence + 1) % 4096;
            return sequence;
        }
    
        function getRandom32BitInteger() {
            return Math.floor(Math.random() * 4294967296);
        }
    
        const t = BigInt(timestamp);
        const shiftedT = t << 23n;
        const seq = getSequence();
        const part1 = shiftedT | BigInt(seq);
        const hex1 = part1.toString(16).padStart(16, '0');
    
        const high = getRandom32BitInteger();
        const low = getRandom32BitInteger();
        const combined = (BigInt(high) << 32n) | BigInt(low);
        const hex2 = combined.toString(16).padStart(16, '0');
    
        return hex1 + hex2;
    }
    function generateB3TraceId() {
        for (var e = "", r = 0; r < 16; r++)
            e += "abcdef0123456789".charAt(Math.floor(16 * Math.random()));
        return e
    }
    // var xhsXhrHeader;
    // const monitorTest = new RequestMonitor();
    // monitorTest.registerHandler('api/sns/web/v2/user/me', async data => {
    //     // console.log("通用请求头获取中...");
    //     if(xhsXhrHeader != null) return;
    //     xhsXhrHeader = data[1].headers;
    //     //"X-S-Common" "X-s"
    // })

    window.addEventListener('load', addPanel);

    function addPanel() {
        const postTool = new PostTool("test", "test");
        var initHtml = `<span>|ω・）请等待网页加载完成~</span>`;
        function createTMIcon() {
            const svgNS = 'http://www.w3.org/2000/svg';
            const svg = document.createElementNS(svgNS, 'svg');
            svg.setAttribute('class', 'tm-icon');
            svg.setAttribute('width', '30%');
            svg.setAttribute('height', '100%');
            svg.setAttribute('p-id', '1616');
            svg.setAttribute('viewBox', '0 0 1024 1024');

            const path1 = document.createElementNS(svgNS, 'path');
            path1.setAttribute('d', 'M903.52 180.7v120.48h-60.22v60.24h-60.24v60.22h-60.24v60.24h-60.24v60.24h-60.22v301.18h120.46v60.22h60.24V1024H240.94v-120.48h60.24v-60.22h120.46V542.12h-60.22v-60.24h-60.24v-60.24h-60.24v-60.22H180.7v-60.24H120.48V180.7h481.88V120.48h-60.24V60.24h60.24V0h120.46v60.24h60.24v60.24h60.24v60.22z');
            path1.setAttribute('p-id', '1617');

            const path2 = document.createElementNS(svgNS, 'path');
            path2.setAttribute('d', 'M602.36 60.24h120.46v60.24h-120.46zM722.82 120.48h60.24v60.22h-60.24zM722.82 240.94v60.24l-30.114 33.942h-60.236L602.36 301.18v-60.24z');
            path2.setAttribute('p-id', '1618');
            path2.setAttribute('fill', '#B3D141');

            const path3 = document.createElementNS(svgNS, 'path');
            path3.setAttribute('d', 'M662.58 361.42l30.126 30.11-30.126 30.11-60.22 60.24-60.24 60.24L512 572.236l-30.12-30.116-60.24-60.24-60.22-60.24-30.12-30.11 30.12-30.11 30.11-26.298h240.94z');
            path3.setAttribute('p-id', '1619');
            path3.setAttribute('fill', '#FFDD55');

            const path4 = document.createElementNS(svgNS, 'path');
            path4.setAttribute('d', 'M783.06 240.94h60.24v60.24h-60.24zM180.7 240.94h361.42v60.24H180.7zM722.82 903.52v60.24H301.18v-60.24h120.46l30.12-30.108 120.476-0.002 30.124 30.11z');
            path4.setAttribute('p-id', '1620');
            path4.setAttribute('fill', '#F1F6F7');

            const path5 = document.createElementNS(svgNS, 'path');
            path5.setAttribute('d', 'M722.82 361.42v60.22h-60.24v-60.22l30.126-26.298zM602.36 421.64h60.22v60.24h-60.22zM542.12 481.88h60.24v60.24h-60.24zM481.88 542.12h60.24v60.24h-60.24zM421.64 481.88h60.24v60.24h-60.24zM361.42 421.64h60.22v60.24h-60.22zM361.42 361.42v60.22h-60.24v-60.22l30.114-30.12z');
            path5.setAttribute('p-id', '1621');
            path5.setAttribute('fill', '#FFF8E1');

            const path6 = document.createElementNS(svgNS, 'path');
            path6.setAttribute('d', 'M240.94 301.18h542.12v60.24H240.94z');
            path6.setAttribute('p-id', '1622');
            path6.setAttribute('fill', '#BA8F02');

            const path7 = document.createElementNS(svgNS, 'path');
            path7.setAttribute('d', 'M602.36 843.3v60.22h-180.72v-60.22h60.24v-180.72h60.24v180.72z');
            path7.setAttribute('p-id', '1623');
            path7.setAttribute('fill', '#C0D9DD');

            [path1, path2, path3, path4, path5, path6, path7].forEach(el => svg.appendChild(el));
            return svg;
        }
        // 创建加载动画
        function createLoadingAnimation() {
            const loading = document.createElement('div');
            loading.className = 'tm-loading';
            return loading;
        }
        /**
         * 
         * @param {string} htmlString - html字符串
         * @returns {Object}
         */
        function handleUserHtml(htmlString){
            const parser = new DOMParser();
            const feedUrl = [];
            const docHtml = parser.parseFromString(htmlString, "text/html");
            docHtml.querySelectorAll("section .cover").forEach(el => {feedUrl.push(el.href)});
            // #userPageContainer .user-ip
            // #userPageContainer .user-desc
            // document.querySelector("div.user-tags svg > use").getAttribute("xlink:href")
            // .user-tags>.tag-item
            // document.querySelectorAll(".user-interactions .count")
            const userIp = docHtml.querySelector("#userPageContainer .user-IP")?.innerText;
            const userDesc = docHtml.querySelector("#userPageContainer .user-desc")?.innerText;
            const userGender = docHtml.querySelector("div.user-tags svg > use")?.getAttribute("xlink:href");
            const userTags = [];
            docHtml.querySelectorAll(".user-tags>.tag-item").forEach(el => {userTags.push(el.innerText)});
            const userInteractions = [];
            docHtml.querySelectorAll(".user-interactions .count").forEach(el => {userInteractions.push(el.innerText)});
            return {
                "feedUrl": feedUrl,
                "userIp": userIp,
                "userDesc": userDesc,
                "userGender": userGender === "#female"?  "女" : "男",
                "userTags": userTags,
                "userInteractions": userInteractions
            };
        }

        function generateSwalInitHtml(data){
            const items = data.map(item => `<li>${item.name}: ${item.count}</li>`).join('');
            return `<ul>${items}</ul>`;
        }

        function generateUserCard(user) {
            return `
              <div class="tm-user-card">
                <div class="tm-user-header">
                  <div class="tm-user-ip">${user.userIp}</div>
                  <div class="tm-user-gender">${user.userGender}</div>
                </div>
          
                <div class="tm-user-desc">${user.userDesc}</div>
          
                <div class="tm-tm-user-tags">
                  ${user.userTags.map(tag => `
                    <div class="tm-user-tag">${tag}</div>
                  `).join('')}
                </div>
          
                <div class="tm-user-interactions">
                  <div class="tm-interaction-item follow">
                    <div class="tm-interaction-count">${user.userInteractions[0]}</div>
                    <div class="tm-interaction-label">关注</div>
                  </div>
                  <div class="tm-interaction-item follower">
                    <div class="tm-interaction-count">${user.userInteractions[1]}</div>
                    <div class="tm-interaction-label">粉丝</div>
                  </div>
                  <div class="tm-interaction-item likes">
                    <div class="tm-interaction-count">${user.userInteractions[2]}</div>
                    <div class="tm-interaction-label">获赞</div>
                  </div>
                </div>
              </div>
            `;
        }

        function addTMIcon(){
            document.querySelectorAll('.note-item .footer .author-wrapper').forEach(footer => {
                if(footer.querySelector('.tm-icon')) return;
                const tmIcon = createTMIcon();
                const loading = createLoadingAnimation();
                loading.classList.add('tm-hide');
                const firstA = footer.querySelector('a');
                firstA.after(tmIcon);
                firstA.after(loading);
            });
        }

        // 事件委托绑定到 #global,寻找一个刷新不受影响的父容器
        function addTMIconFuc(){
            document.querySelector("#global").addEventListener('click', async function (event) {
                const icon = event.target.closest('.tm-icon'); // 判断是否点击图标或其子元素
                if (!icon || !icon.closest('#exploreFeeds')) return;
                event.stopPropagation(); // 阻止事件冒泡到父元素
                icon.classList.add('tm-hide');
                const footer = icon.closest('.author-wrapper');
                const loading = footer.querySelector('.tm-loading'); // 判断是否点击图标或其子元素
                loading.classList.remove('tm-hide');
                if (footer) {
                    const href = footer.querySelector('a').href;
                    try {
                        const user_html = await postTool.getPostUserInfo(href);
                        const userInfoRes = handleUserHtml(user_html);
                        const feedUrl = userInfoRes["feedUrl"];
                        const promises = feedUrl.map(url => postTool.getPostContent(url));
                        await new Promise(resolve => setTimeout(resolve, 500)); // 模拟耗时
                        const results = await Promise.all(promises);
                        const postStatistics = new PostStatistics();
                        for(const result of results){
                            const postProcessor = new PostProcessor(result);
                            postStatistics.addTag(postProcessor.getTags());
                        }
                        const sortedByCountDesc = postStatistics.sortTagStats(
                            postStatistics.countTags(),
                            'count',
                            'desc');
                        
                        initHtml = generateUserCard({
                            "userIp": userInfoRes["userIp"],
                            "userGender": userInfoRes["userGender"],
                            "userDesc": userInfoRes["userDesc"],
                            "userTags": userInfoRes["userTags"],
                            "userInteractions": userInfoRes["userInteractions"]
                        });
                        initHtml += generateSwalInitHtml(sortedByCountDesc);
                      } catch (error) {
                        console.error("获取用户信息失败:", error);
                    }
                    
                }
                icon.classList.remove('tm-hide');
                loading.classList.add('tm-hide');
                if (!icon.classList.contains('tm-clicked')) {
                    icon.classList.add('tm-clicked');
                }
                openPanelFunc();
            });
        }
        // 监听 DOM 变化，自动注入图标
        function observeNewItems() {
            const targetNode = document.getElementById('mfContainer');
            if (!targetNode) return;

            const observer = new MutationObserver((mutationsList) => {
                for (const mutation of mutationsList) {
                    if (mutation.type === 'childList') {
                        addTMIcon();
                    }
                }
            });

            observer.observe(targetNode, { childList: true, subtree: true,});
        }

        async function openPanelFunc() {
            const { value: formValues } = await Swal.fire({
                position: "center-end",
                draggable: true,
                backdrop: false,
                title: "<strong>主页标签统计</strong>",
                showCancelButton: true,
                cancelButtonText: 'Cancel',
                confirmButtonText: 'Confirm',
                //class="swal2-range" swalalert框架可能会对其有特殊处理，导致其内标签的id声明失效
                html: initHtml,
                focusConfirm: false
            });
        }

        addTMIcon();
        addTMIconFuc();
        observeNewItems();
        // var intervalID = setInterval(addTMIcon, 2000)

        var css_text = `
            .tm-icon {
                transition: transform 0.3s ease;
            }
            .tm-icon:hover {
                transform: scale(1.1);
                cursor: pointer;
                filter: brightness(80%); /* 降低亮度，使图片变暗 */
            }
            .tm-clicked{
                filter: grayscale(100%);
            }
            .tm-hide {
                display: none;
            }
            .tm-loading {
                position: relative;
                width: 15px;
                height: 15px;
                border: 2px solid rgba(255, 87, 34, 0.2); /* 活力橙色半透明 */
                border-top-color: #FF5722; /* 活力橙色 */
                border-radius: 100%;
                animation: tmCircle infinite 0.75s linear;
            }
            @keyframes tmCircle {
                0% { transform: rotate(0); }
                100% { transform: rotate(360deg); }
            }
            /* 固定复选框容器高度并添加滚动条 */
            #swal2-html-container {
                max-height: 200px;  /* 可根据需求调整高度 */
                overflow-y: auto;    /* 垂直滚动条 */
                overflow-x: hidden;  /* 隐藏水平滚动条 */
                padding-right: 15px; /* 防止滚动条遮挡内容 */
                border: 1px solid #ddd; /* 可选：添加边框 */
            }

            /* 可选：优化滚动条样式（仅现代浏览器支持） */
            #swal2-html-container::-webkit-scrollbar {
                width: 8px; /* 滚动条宽度 */
            }

            .tm-user-card {
              max-width: 400px;
              background: #ffffff;
              border-radius: 12px;
              box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08);
              padding: 24px;
              font-family: 'Segoe UI', sans-serif;
              line-height: 1.6;
              color: #333;
            }
      
            .tm-user-header {
              display: flex;
              justify-content: space-between;
              align-items: center;
              margin-bottom: 16px;
              flex-wrap: wrap;
            }
      
            .tm-user-ip {
              font-size: 14px;
              color: #666;
              display: flex;
              align-items: center;
              gap: 6px;
            }
      
            .tm-user-gender {
              font-size: 14px;
              color: #ff4081;
              font-weight: 500;
            }
      
            .tm-user-desc {
              font-size: 15px;
              color: #444;
              margin-bottom: 20px;
              line-height: 1.6;
            }
      
            .tm-tm-user-tags {
              display: flex;
              flex-wrap: wrap;
              gap: 8px;
              margin-bottom: 20px;
            }
      
            .tm-user-tag {
              background: #f0f0f0;
              padding: 3px 10px;
              border-radius: 10px;
              font-size: 12px;
              color: #555;
            }
      
            .tm-user-interactions {
              display: flex;
              gap: 20px;
              font-size: 14px;
              color: #555;
            }
      
            .tm-interaction-item {
              text-align: center;
            }
      
            .tm-interaction-count {
              font-size: 18px;
              font-weight: 600;
              color: #222;
              margin-bottom: 4px;
              display: flex;
              align-items: center;
              justify-content: center;
              gap: 6px;
            }
      
            .tm-interaction-label {
              font-size: 12px;
              color: #888;
            }

        `
        GMaddStyle(css_text);
    }

    function GMaddStyle(css) {
        var myStyle = document.createElement('style');
        myStyle.textContent = css;
        var doc = document.head || document.documentElement;
        doc.appendChild(myStyle);
    }

    /**
     * 
     * @param {string} url - url路径
     * @returns {Object<string, string>} - url参数和路径
     */
    function parseUrlParamsAndPath(url) {
        const urlObj = new URL(url);
        const result = {};
      
        // 1. 提取查询参数
        for (const [key, value] of urlObj.searchParams.entries()) {
          result[key] = value;
        }
      
        // 2. 提取路径中的 source_note_id（最后一个路径段）
        const pathSegments = urlObj.pathname.split('/').filter(Boolean);
        if (pathSegments.length > 0) {
          result.source_note_id = pathSegments[pathSegments.length - 1];
        }
      
        return result;
      }
})();